package com.norbitltd.guavaleaves.generator;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Function;
import com.google.common.collect.Lists;

public class PredicatesGenerator extends Generator {

	public static void main(String[] args) throws ClassNotFoundException {
		new PredicatesGenerator(java.lang.Class.class).generate();
	}

	private StringBuilder builder;
	private Class<?> clazz;

	public PredicatesGenerator(Class<?> clazz) {
		this.clazz = clazz;
	}

	public void generate() {
		try {
			builder = new StringBuilder();
			System.out.println("Are you generating for the base class (y/n)?");
			boolean isBaseClass = reader.readLine().toLowerCase().equals("y");
			if (isBaseClass) {
				generatePredicatesForBaseClass();
			} else {
				generatePredicatesForUtilityClass();
			}
		} catch (Exception e) {
			System.out.println("Exception thrown during predicates generation for class: "
					+ clazz.getName() + ", cause: " + e.getMessage());
			e.printStackTrace(System.out);
		} finally {
			System.out.println(builder.toString()); 
		}

	}

	private void generatePredicatesForBaseClass() throws IOException {
		for (Method method : clazz.getMethods()) {
			if (!method.getName().equals("equals") &&
					(method.getReturnType().equals(boolean.class) || method.getReturnType().equals(Boolean.class))) {
				System.out.println("Generate for method " + method.getName() + " (y/n or 'q' to quit)?");
				String option = reader.readLine().toLowerCase();
				
				if( option.equals("q")) {
					return;
				} else if( option.equals("y") ) {
					generatePredicateForBaseClassMethod(method);
				}
			}
		}
	}
	
	private void generatePredicateForBaseClassMethod(Method method) throws IOException {
		if( ArrayUtils.isEmpty( method.getParameterTypes() ) ) {
			
			System.out.println("Processing non-parametrized method " + method.getName() + "...");
			builder.append("\npublic static Predicate<" + clazz.getSimpleName() + "> " + method.getName() + "() {");
			builder.append("\n\treturn new Predicate<" + clazz.getSimpleName() + ">() {");
			builder.append("\n\t\tpublic boolean apply(" + clazz.getSimpleName() + " input) {");
			builder.append("\n\t\t\treturn input." + method.getName() + "();");
			builder.append("\n\t\t}");
			builder.append("\n\t};");
			builder.append("\n}\n");
			System.out.println("Processed non-parametrized method " + method.getName() + "!");
			
		} else {
			
			System.out.println("Processing parametrized method " + method.getName() + "...");
			List<String> parameters = generateParametersForBaseClassMethod(method);
			String finalParametersString = "final " + StringUtils.join(parameters, ", final ");
			String parameterNamesString = StringUtils.join(
					Lists.transform(parameters, new Function<String, String>() {
						public String apply(String input) {
							return input.split(" ")[1];
						}}), ", ");
			
			builder.append("\npublic static Predicate<" + clazz.getSimpleName() + "> " + method.getName() + "( " + 
					finalParametersString + " ) {");
			builder.append("\n\treturn new Predicate<" + clazz.getSimpleName() + ">() {");
			builder.append("\n\t\tpublic boolean apply(" + clazz.getSimpleName() + " input) {");
			builder.append("\n\t\t\treturn input." + method.getName() + "(" + parameterNamesString + ");");
			builder.append("\n\t\t}");
			builder.append("\n\t};");
			builder.append("\n}\n");
			System.out.println("Processed non-parametrized method " + method.getName() + "!");
			
		}
	}
	
	private List<String> generateParametersForBaseClassMethod(Method method) throws IOException {
		List<String> parameters = Lists.newArrayList();
		Class<?>[] parameterTypes = method.getParameterTypes();
		
	
		for(Class<?> parameterType : parameterTypes) {
			String defaultName = StringUtils.uncapitalize(parameterType.getSimpleName());
			System.out.println("Parameter " + (parameters.size() + 1) + " of " + parameterTypes.length + 
					": please type parameter name (default: " + defaultName + ")\n");
			
			String parameterName = reader.readLine();
			parameterName = StringUtils.isBlank(parameterName) ? defaultName : parameterName;
			parameters.add(parameterType.getSimpleName() + " " + parameterName);
		}
		
		return parameters;
	}

	private void generatePredicatesForUtilityClass() {
		throw new RuntimeException("Not yet implemented!");
	}
}
